<script setup lang="ts">
import { uploadFileApi } from '@/api/file'

const imgUrl = ref('')
const canvas = ref()
let ctx: CanvasRenderingContext2D

// 正在绘制中，用来控制 move 和 end 事件
let painting = false

// 获取触发点相对被触发dom的左、上角距离
const getOffset = (event: MouseEvent | TouchEvent) => {
  let offset: [number, number]
  if ((event as MouseEvent).offsetX) {
    // pc端
    const { offsetX, offsetY } = event as MouseEvent
    offset = [offsetX, offsetY]
  } else {
    // 移动端
    const { top, left } = canvas.value.getBoundingClientRect()
    const offsetX = (event as TouchEvent).touches[0].clientX - left
    const offsetY = (event as TouchEvent).touches[0].clientY - top
    offset = [offsetX, offsetY]
  }

  return offset
}

// 绘制起点
let startX = 0,
  startY = 0

// 鼠标/触摸 按下时，保存 触发点相对被触发dom的左、上 距离
const onEventStart = (event: MouseEvent | TouchEvent) => {
  ;[startX, startY] = getOffset(event)
  painting = true
}

const onEventMove = (event: MouseEvent | TouchEvent) => {
  if (painting) {
    // 鼠标/触摸 移动时，保存 移动点相对 被触发dom的左、上 距离
    const [endX, endY] = getOffset(event)
    paint(startX, startY, endX, endY, ctx)

    // 每次绘制 或 清除结束后，起点要重置为上次的终点
    startX = endX
    startY = endY
  }
}

const onEventEnd = () => {
  if (painting) {
    painting = false // 停止绘制
  }
}

onMounted(() => {
  ctx = canvas.value.getContext('2d') as CanvasRenderingContext2D
})
const handleToFile = async () => {
  if (isCanvasBlank(canvas.value)) {
    ElMessage({
      type: 'warning',
      message: '当前签名文件为空'
    })
    return
  }
  const file = dataURLtoFile(canvas.value.toDataURL(), '签名.png')

  if (!file) return
  const { data } = await uploadFileApi(file)
  handleClearSign()
  imgUrl.value = data.url
}
const handleClearSign = () => {
  ctx.clearRect(0, 0, canvas.value.width, canvas.value.height)
}
const isCanvasBlank = (canvas: HTMLCanvasElement) => {
  const blank = document.createElement('canvas') //系统获取一个空canvas对象
  blank.width = canvas.width
  blank.height = canvas.height
  return canvas.toDataURL() == blank.toDataURL() //比较值相等则为空
}

// 保存为图片
const handleSaveImg = () => {
  if (isCanvasBlank(canvas.value)) {
    ElMessage({
      type: 'warning',
      message: '当前签名文件为空'
    })
    return
  }
  const el = document.createElement('a')
  // 设置 href 为图片经过 base64 编码后的字符串，默认为 png 格式
  el.href = canvas.value.toDataURL()
  el.download = '签名'
  // 创建一个点击事件并对 a 标签进行触发
  const event = new MouseEvent('click')
  el.dispatchEvent(event)
}
// 转为file格式，可传递给后端
const dataURLtoFile = (dataurl: string, filename: string) => {
  const arr: string[] = dataurl.split(',')
  if (!arr.length) return

  const mime = arr[0].match(/:(.*?);/)
  if (mime) {
    const bstr = atob(arr[1])
    let n = bstr.length
    const u8arr = new Uint8Array(n)
    while (n--) {
      u8arr[n] = bstr.charCodeAt(n)
    }
    return new File([u8arr], filename, { type: mime[1] })
  }
}
// canvas 画图
function paint(
  startX: number,
  startY: number,
  endX: number,
  endY: number,
  ctx: CanvasRenderingContext2D
) {
  ctx.beginPath()
  ctx.globalAlpha = 1
  ctx.lineWidth = 2
  ctx.strokeStyle = '#000'
  ctx.moveTo(startX, startY)
  ctx.lineTo(endX, endY)
  ctx.closePath()
  ctx.stroke()
}
</script>
<template>
  <div class="canvas-dom">
    <h3>基于canvas实现的签名组件</h3>
    <header>
      <el-button type="primary" @click="handleSaveImg">保存为图片</el-button>
      <el-button @click="handleToFile"> 保存到后端 </el-button>
      <el-button @click="handleClearSign"> 清空签名 </el-button>
    </header>
    <canvas
      ref="canvas"
      height="200"
      width="500"
      @mousedown="onEventStart"
      @mousemove.stop.prevent="onEventMove"
      @mouseup="onEventEnd"
      @touchstart="onEventStart"
      @touchmove.stop.prevent="onEventMove"
      @touchend="onEventEnd"
    >
    </canvas>
    <img v-if="imgUrl" :src="imgUrl" alt="签名" />
  </div>
</template>
<style scoped lang="scss">
.canvas-dom {
  width: 100%;
  height: 100%;
  padding: 0 20px;
  background-color: #fff;

  canvas {
    border: 1px solid #e6e6e6;
  }

  header {
    display: flex;
    flex-flow: row nowrap;
    align-items: center;
    width: 100%;
    margin: 8px;

    .eraser-option {
      display: flex;

      label {
        white-space: nowrap;
      }
    }
  }
}
</style>
